/**
 * 
 */
package com.goldentrail.neo4j;

import java.util.ArrayList;

import org.json.JSONException;
import org.json.JSONObject;

/**
 * Specification for doing Neo4j traversals.
 * 
 * @author mikeagun
 * 
 */
public class TraverserSpec {
	public static final String DEPTH_FIRST = "depth_first";
	public static final String BREADTH_FIRST = "breadth_first";

	public static final String NODE_PATH_UNIQUENESS = "node_path";
	public static final String NODE_GLOBAL_UNIQUENESS = "node_global";
	public static final String NONE_UNIQUENESS = "none";
	public static final String RELATIONSHIP_GLOBAL_UNIQUENESS = "relationship_global";
	public static final String RELATIONSHIP_PATH_UNIQUENESS = "relationship_path";

	public static final String ALL_RETURN_FILTER = "all";
	public static final String ALL_BUT_START_FILTER = "all_but_start_node";

	public static final String BUILTIN_LANG = "builtin";
	public static final String JAVASCRIPT_LANG = "javascript";

	private static final String RETURN_FILTER = "return_filter";
	private static final String ORDER = "order";
	private static final String UNIQUENESS = "uniqueness";
	private static final String PRUNE_EVALUATOR = "prune_evaluator";
	private static final String RELATIONSHIPS = "relationships";
	private static final String MAX_DEPTH = "max_depth";

	private String order = null;
	private String uniqueness = null;
	private ArrayList<RelationshipSpec> relationships = null;
	private String pruneEvaluatorLanguage = null;
	private String pruneEvaluatorBody = null;
	private String returnFilterLanguage = null;
	private String returnFilterName = null;
	private String returnFilterBody = null;
	/**
	 * Max depth is a short hand way of specifying a prune evaluator which
	 * prunes after a certain depth (1 is default, none if prune evaluator
	 * specified instead)
	 */
	private int maxDepth = -1;

	private Boolean maxDepthSet = false;

	/**
	 * Gets the return filter language.
	 * 
	 * @return the return filter language
	 */
	public String getReturnFilterLanguage() {
		return returnFilterLanguage;
	}

	/**
	 * Sets the return filter language. common examples are the constants
	 * JAVASCRIPT_LANG and BUILTIN_LANG.
	 * 
	 * @param returnFilterLanguage
	 *            the return filter language to set
	 */
	public void setReturnFilterLanguage(String returnFilterLanguage) {
		this.returnFilterLanguage = returnFilterLanguage;
	}

	/**
	 * Gets the return filter name.
	 * 
	 * @return the return filter name
	 */
	public String getReturnFilterName() {
		return returnFilterName;
	}

	/**
	 * Sets the return filter name. only use with BUILTIN_LANG, leave as null
	 * otherwise. examples are the constants ALL_RETURN_FILTER,
	 * ALL_BUT_START_FILTER
	 * 
	 * @param returnFilterName
	 *            the return filter name to set
	 */
	public void setReturnFilterName(String returnFilterName) {
		this.returnFilterName = returnFilterName;
	}

	/**
	 * Gets the return filter body.
	 * 
	 * @return the return filter body
	 */
	public String getReturnFilterBody() {
		return returnFilterBody;
	}

	/**
	 * Sets the return filter body. don't use with BUILTIN_LANG, leave as null
	 * for that case. should be a valid expression in the specified language. in
	 * node traversals gets called on each node, in path traversals gets called
	 * on each path.
	 * 
	 * @param returnFilterName
	 *            the return filter name to set
	 */
	public void setReturnFilterBody(String returnFilterBody) {
		this.returnFilterBody = returnFilterBody;
	}

	/**
	 * Sets the whole return filter. Leave the arguments you don't want as null
	 * 
	 * @param name
	 *            the return filter name to use (ALL_RETURN_FILTER,
	 *            ALL_BUT_START_FILTER, null)
	 * @param language
	 *            the return filter language to use (BUILTIN_LANG,
	 *            JAVASCRIPT_LANG)
	 * @param body
	 *            the return filter body to use(valid expression in language,
	 *            null)
	 */
	public void setReturnFilter(String name, String language, String body) {
		this.returnFilterLanguage = language;
		this.returnFilterBody = body;
		this.returnFilterName = name;
	}

	/**
	 * Default constructor. does no additional setup.
	 */
	public TraverserSpec() {

	}

	/**
	 * Gets the JSONObject representing a path specification that can then be sent to the server to initiate traversal
	 * @return JSONObject representing traversal specification
	 */
	public JSONObject getJson() {
		JSONObject json = new JSONObject();

		JSONObject returnFilter = new JSONObject();
		try {
			if (order != null)
				json.put(ORDER, order);
			if (uniqueness != null)
				json.put(UNIQUENESS, uniqueness);
			if (relationships != null) {
				for (RelationshipSpec rel : relationships) {
					json.accumulate(RELATIONSHIPS, rel.getJson());
				}
			}
			if (pruneEvaluatorLanguage != null && pruneEvaluatorBody != null) {
				JSONObject pruneEvaluator = new JSONObject();
				pruneEvaluator.put("language", pruneEvaluatorLanguage);
				pruneEvaluator.put("body", pruneEvaluatorBody);
				json.put(PRUNE_EVALUATOR, pruneEvaluator);
			}
			if (returnFilterLanguage != null
					&& (returnFilterName != null || returnFilterBody != null)) {
				returnFilter.put("language", returnFilterLanguage);
				if (returnFilterName != null)
					returnFilter.put("name", returnFilterName);
				if (returnFilterBody != null)
					returnFilter.put("body", returnFilterBody);
				json.put(RETURN_FILTER, returnFilter);
			}
			if (maxDepthSet)
				json.put(MAX_DEPTH, maxDepth);

		} catch (JSONException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return json;
	}
	/*
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString() {
		JSONObject json = getJson();
		if (json==null)
			return null;
		else
			return json.toString();
	}

	/**
	 * @return the relationships to traverse
	 */
	public ArrayList<RelationshipSpec> getRelationships() {
		return relationships;
	}

	/**
	 * Sets the relationships to traverse
	 * 
	 * @param relationships
	 *            the relationships to set
	 */
	public void setRelationships(ArrayList<RelationshipSpec> relationships) {
		this.relationships = relationships;
	}

	/**
	 * @return the order
	 */
	public String getOrder() {
		return order;
	}

	/**
	 * Sets the order used in traversing. should be DEPTH_FIRST or
	 * BREADTH_FIRST.
	 * 
	 * @param order
	 *            the order to set
	 */
	public void setOrder(String order) {
		this.order = order;
	}

	/**
	 * @return the uniqueness
	 */
	public String getUniqueness() {
		return uniqueness;
	}

	/**
	 * Sets the uniqueness used in returning nodes. Should be one of the
	 * contants that end with _UNIQUENESS
	 * 
	 * @param uniqueness
	 *            the uniqueness to set
	 */
	public void setUniqueness(String uniqueness) {
		this.uniqueness = uniqueness;
	}

	/**
	 * @return the maxDepth
	 */
	public int getMaxDepth() {
		return maxDepth;
	}

	/**
	 * Sets the depth limit on the traversal. Can be left unset if a prune
	 * evaluator is used. defaults to one if no max depth or prune evaluator is
	 * set
	 * 
	 * @param maxDepth
	 *            the maxDepth to set
	 */
	public void setMaxDepth(int maxDepth) {
		maxDepthSet = true;
		this.maxDepth = maxDepth;
	}

	/**
	 * Gets the prune evaluator language.
	 * 
	 * @return the prune evaluator language
	 */
	public String getPruneEvaluatorLanguage() {
		return pruneEvaluatorLanguage;
	}

	/**
	 * Sets the prune evaluator language. There are no builtin, so use
	 * JAVASCRIPT_LANG or another scripting language. can leave as null and just
	 * use max depth.
	 * 
	 * @param pruneEvaluatorLanguage
	 *            the prune evaluator language to set
	 */
	public void setPruneEvaluatorLanguage(String pruneEvaluatorLanguage) {
		this.pruneEvaluatorLanguage = pruneEvaluatorLanguage;
	}

	/**
	 * Gets the prune evaluator body.
	 * 
	 * @return the prune evaluator body
	 */
	public String getPruneEvaluatorBody() {
		return pruneEvaluatorBody;
	}

	/**
	 * Sets the prune evaluator language. Should be a valid expression in the
	 * specified prune evaluator language or left null if no prune evaluator is
	 * to be used.
	 * 
	 * @param pruneEvaluatorLanguage
	 *            the prune evaluator language to set
	 */
	public void setPruneEvaluatorBody(String pruneEvaluatorBody) {
		this.pruneEvaluatorBody = pruneEvaluatorBody;
	}

	/**
	 * sets the prune evaluator used to end traversal
	 * 
	 * @param lang
	 *            language used to specify body
	 * @param body
	 *            body of the prune evaluator
	 */
	public void setPruneEvaluator(String lang, String body) {
		pruneEvaluatorLanguage = lang;
		pruneEvaluatorBody = body;
	}
}
